package de.g18.scavengerhunt.activity;

import java.util.Iterator;
import java.util.List;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;

import de.g18.scavengerhunt.AppApplicationContext;
import de.g18.scavengerhunt.AppConstants;
import de.g18.scavengerhunt.R;
import de.g18.scavengerhunt.overlay.UserOverlay;
import de.g18.scavengerhunt.service.CompassGateway;
import de.g18.scavengerhunt.service.GeoDataGateway;
import de.g18.scavengerhunt.service.ReceivePositionThread;
import de.g18.scavengerhunt.service.SendPositionThread;
import de.g18.scavengerhunt.types.BattleZone;
import de.g18.scavengerhunt.types.User;
import de.g18.scavengerhunt.utils.GameUtils;
import de.g18.scavengerhunt.utils.NotificationUtil;

/**
 * This is the layout for the game map, also containing some game logic
 */
public class GameMapActivity extends MapActivity implements MapActivityInterface {

    private String         LOG_TAG              = this.getClass().getSimpleName();

    private MapView        mapView              = null;

    private UserOverlay    selfOverlay          = null;

    private UserOverlay    memberOverlay        = null;

    private UserOverlay    evaderOverlay        = null;

    private GeoDataGateway geoDataGateway       = null;

    private CompassGateway  compasGateway        = null;

    private TextView       lblSessionName       = null;

    private Button         btnLeaveGame         = null;

    private Button         btnShowOtherProfiles = null;

    private TextView       lblUsername          = null;

    private TextView       lblNumPlayers        = null;

    private TextView       lblDistance          = null;

    private int            distanceToEvader     = 0;

    private boolean        isWinButtonSet       = false;

    private boolean        isEvaderInSession     = false;

    private boolean        inBattleZone         = false;

    private BattleZone     battleZone           = null;

    private Drawable       selfPosDrawable      = null;

    private Drawable       memberPosDrawable    = null;

    private Drawable       evaderPosDrawable    = null;

    /*
     * (@Override)
     * @see com.google.android.maps.MapActivity#onCreate(android.os.Bundle)
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.map);

        // start the notification util and display 'game active' notification'
        NotificationUtil.initialize(this);
        NotificationUtil.showGameRunningNotification();

        // Show data of current session.
        lblSessionName = (TextView) findViewById(R.id.txtSessionName);
        btnLeaveGame = (Button) findViewById(R.id.btnLeaveGame);
        btnShowOtherProfiles = (Button) findViewById(R.id.btnShowOtherProfiles);
        lblUsername = (TextView) findViewById(R.id.lblUsername);
        lblNumPlayers = (TextView) findViewById(R.id.lblPlayers);
        lblDistance = (TextView) findViewById(R.id.lblDistanceToEvader);

        if (AppApplicationContext.getUser().getId() == AppApplicationContext.getSession().getEvaderUserId()) {
            lblDistance.setText(getString(R.string.youAreTheEvader));
        }

        lblUsername.setText(getString(R.string.username) + " " + AppApplicationContext.getUser().getName());
        btnLeaveGame.setOnClickListener(getLeaveGameOnClickListener());
        btnShowOtherProfiles.setOnClickListener(getShowOtherProfilesOnClickListener());
        lblSessionName.setText(AppApplicationContext.getSession().getName());

        // Start threads to send and receive positions.
        AppApplicationContext.setSendPositionThread(new Thread(new SendPositionThread(AppApplicationContext.getUser().getId())));
        AppApplicationContext.getSendPositionThread().start();
        AppApplicationContext.setReceivePositionThread(new Thread(new ReceivePositionThread(AppApplicationContext.getSession().getId(), this)));
        AppApplicationContext.getReceivePositionThread().start();

        mapView = (MapView) findViewById(R.id.mapview);
        mapView.setBuiltInZoomControls(true);
        mapView.setSatellite(false);

        battleZone = AppApplicationContext.getSession().getBattleZone();

        selfPosDrawable = getResources().getDrawable(R.drawable.arrow);
        memberPosDrawable = getResources().getDrawable(R.drawable.agent);
        evaderPosDrawable = getResources().getDrawable(R.drawable.user_basecap);

        selfOverlay = new UserOverlay(selfPosDrawable, this);
        memberOverlay = new UserOverlay(memberPosDrawable, this);
        evaderOverlay = new UserOverlay(evaderPosDrawable, this);

        List<Overlay> allOverlays = mapView.getOverlays();
        allOverlays.add(battleZone.getCircleOverlay());
        allOverlays.add(selfOverlay);
        allOverlays.add(memberOverlay);
        allOverlays.add(evaderOverlay);

        // Zoom to fit the BattleZone on screen.
        // int zoomLevel = battleZone.getZoomLevelToFitZone();
        // if(zoomLevel > mapView.getZoomLevel()) {
        // mapView.getController().setZoom(zoomLevel);
        // mapView.getController().animateTo(battleZone.getCenterPosition());
        // }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        NotificationUtil.dismissNotifications();
        NotificationUtil.destroy();
    }

    @Override
    protected boolean isRouteDisplayed() {
        // this method is required by interface and is not used
        return false;
    }

    /**
     * Handles overlay update messages.
     * 
     * @param msg
     */
    public void onOverlayUpdate(Message msg) {
        overlayUpdateHandler.sendMessage(msg);
    }

    /**
     * This method handles position and compass updates from the corresponding threads
     */
    private Handler overlayUpdateHandler = new Handler() {
                                             public void handleMessage(Message msg) {
                                                 try {
                                                     switch (msg.what) {
                                                         case AppConstants.UPDATE_OWN_POSITION:
                                                             updateOwnPosition(msg);
                                                             break;
                                                         case AppConstants.UPDATE_POSITION_FROM_SERVICE:
                                                             updateOtherPositions(msg);
                                                             break;
                                                         case AppConstants.UPDATE_COMPASS:
                                                             updateCompas(msg);
                                                             break;
                                                     }
                                                 } catch (Exception e) {
                                                     Log.e(LOG_TAG, "Exception occured while updating overlays: " + e.getMessage());
                                                     e.printStackTrace();
                                                 }

                                                 // Check if the user can win the game.
                                                 isEvaderFoundBtnVisible();
                                                 if (AppApplicationContext.getSession() != null) {
                                                     if (!isEvaderInSession && AppApplicationContext.getUser().getId() != AppApplicationContext.getSession().getEvaderUserId()) {
                                                         lblDistance.setText(getString(R.string.evaderNotExisting));
                                                     }
                                                 }
                                                 super.handleMessage(msg);
                                             }

                                             /**
                                              * Updates position of other users.
                                              * 
                                              * @param msg
                                              */
                                             private void updateOtherPositions(Message msg) {
                                                 @SuppressWarnings("unchecked")
                                                 List<User> users = (List<User>) msg.obj;
                                                 // Update the shown number of players.
                                                 lblNumPlayers.setText(users.size() + " Spieler");
                                                 Iterator<User> userIterator = users.iterator();
                                                 while (userIterator.hasNext()) {
                                                     User user = userIterator.next();

                                                     // Remove own user from list
                                                     if (user.getId() == AppApplicationContext.getUser().getId()) {
                                                         userIterator.remove();
                                                     } else if (user.getId() == AppApplicationContext.getSession().getEvaderUserId()) {
                                                         isEvaderInSession = true;

                                                         // Draw evader (if not the user of the device) and remove its user instance from the list
                                                         setEvaderPosition(user.getCurrentGeoPoint(), user.getName(), "Position: Lat=" + String.valueOf(user.getCurrentGeoPoint().getLatitudeE6()));

                                                         // Update distance information shown to the user.
                                                         distanceToEvader = GameUtils.getDistanceBetweenPoints(AppApplicationContext.getUser().getCurrentGeoPoint(), user.getCurrentGeoPoint());
                                                         lblDistance.setText(getString(R.string.distanceToEvader) + " " + distanceToEvader + " Meter");
                                                         userIterator.remove();
                                                     }
                                                 }

                                                 // Draw members
                                                 setMemberPositionDrawables(users);
                                             }
                                         };

    /**
     * Updates the compass arrow on the map
     * 
     * @param msg
     */
    private void updateCompas(Message msg) {
        int azimuth = (Integer) msg.obj;
        selfOverlay.rotateDrawable(selfPosDrawable, azimuth);
        Log.d(LOG_TAG, "Grad: " + azimuth);
        mapView.invalidate();
    }

    private void setSelfPosition(GeoPoint geoPoint, String title, String snippet) {
        selfOverlay.clear();
        selfOverlay.addDrawable(selfPosDrawable, geoPoint, title, snippet);
        selfOverlay.rotateDrawable(selfPosDrawable, compasGateway.getLastAzimuth());
    }

    private void setMemberPositionDrawables(List<User> userList) {
        memberOverlay.clear();
        for (User user : userList) {
            memberOverlay.addDrawable(memberPosDrawable, user.getCurrentGeoPoint(), user.getName(),
                    "Position: Lat=" + String.valueOf(user.getCurrentGeoPoint().getLatitudeE6()) + ", Lng=" + String.valueOf(user.getCurrentGeoPoint().getLongitudeE6()));
        }
    }

    private void setEvaderPosition(GeoPoint geoPoint, String title, String snippet) {
        evaderOverlay.clear();
        evaderOverlay.addDrawable(evaderPosDrawable, geoPoint, title, snippet);
    }

    private OnClickListener getShowOtherProfilesOnClickListener() {
        return new OnClickListener() {

            @Override
            public void onClick(View v) {
                GameUtils.showActivity(new OtherProfilesActivity());
            }
        };
    }

    private OnClickListener getLeaveGameOnClickListener() {
        return new OnClickListener() {

            @Override
            public void onClick(View v) {
                showDialog(AppConstants.DIALOG_LEAVE_GAME_MESSAGE);
            }
        };
    }

    /*
     * (@Override)
     * @see android.app.Activity#onCreateDialog(int)
     */
    @Override
    protected Dialog onCreateDialog(int id) {
        Dialog dialog = null;

        switch (id) {
            case AppConstants.DIALOG_LEAVE_GAME_MESSAGE:
                dialog = new AlertDialog.Builder(this).setMessage(getString(R.string.dialogLeaveGameMessage)).setTitle(getString(R.string.dialogLeaveGameTitle))
                        .setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                if (AppApplicationContext.getSession() != null) {
                                    if (!AppApplicationContext.getSession().leave()) {
                                        // TODO Show dialog that leaving the session has failed.
                                    } else {
                                        AppApplicationContext.setSession(null);
                                        geoDataGateway.stop();
                                        compasGateway.stop();
                                        GameUtils.showActivity(new GamesActivity());
                                    }
                                }
                            }
                        }).setNegativeButton(getString(R.string.no), new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.cancel();
                            }
                        }).create();
                break;
        }

        return dialog;
    }

    /**
     * This override makes sure, that the device back button only intents to load the GamesActivity.
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
            GameUtils.showActivity(new GamesActivity());
            return false;
        }

        return super.onKeyDown(keyCode, event);
    }

    /**
     * Checks if the conditions are true to win the game.
     */
    private void isEvaderFoundBtnVisible() {
        if (!inBattleZone) {
            return;
        }
        LinearLayout mapLayout = (LinearLayout) findViewById(R.id.mapLayout);
        if (distanceToEvader <= AppConstants.DISTANCE_TO_EVADER_TO_WIN && !isWinButtonSet && isEvaderInSession) {
            Button btnWinGame = new Button(this);
            btnWinGame.setText(getString(R.string.evaderFound));
            btnWinGame.setId(999);
            btnWinGame.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    Toast.makeText(getApplicationContext(), getString(R.string.youAreTheWinner), Toast.LENGTH_SHORT).show();
                    NotificationUtil.showGameOverNotification();
                    // TODO: Send request to server to end the session.
                }
            });

            mapLayout.addView(btnWinGame, 1);
            isWinButtonSet = true;
        } else if (distanceToEvader > AppConstants.DISTANCE_TO_EVADER_TO_WIN && isWinButtonSet) {
            if (mapLayout.findViewById(999) != null) {
                mapLayout.removeViewAt(1);
                isWinButtonSet = false;
            }
        }
    }

    /**
     * Updates position of current user.
     * 
     * @param msg
     */
    private void updateOwnPosition(Message msg) {
        GeoPoint currentOwnPosition = (GeoPoint) msg.obj;
        setSelfPosition(currentOwnPosition, "UserID: " + AppApplicationContext.getUser().getId(), null);

        inBattleZone = AppApplicationContext.getSession().getBattleZone().isInBattleZone(AppApplicationContext.getUser());
        if (!inBattleZone) {
            NotificationUtil.showOutOfBattleZoneNotification();
        }
    }

    /**
     * Called automatically when the Activity has been created and will be visible to the user.
     */
    @Override
    protected void onStart() {
        super.onStart();

        // Give the ApplicationContext the current activity instance.
        AppApplicationContext.setActivity(this);

        // Start using GPS Device
        geoDataGateway = new GeoDataGateway(this);
        geoDataGateway.start();

        // Start using magnetic field device
        compasGateway = new CompassGateway(this);
        compasGateway.start();
    }

    /**
     * Will be called when the activity gets active again.
     */
    @Override
    protected void onResume() {
        super.onResume();
        compasGateway.start();
    }

    /**
     * Will be called when the activity isn't visible.
     */
    @Override
    protected void onPause() {
        super.onPause();
        compasGateway.stop();
    }
}
